package it.tukano.swingdeco.list;

import it.tukano.fun.Function1;
import it.tukano.fun.Function2;
import it.tukano.swingdeco.event.InputAdapter;
import it.tukano.swingdeco.event.MouseEventExt;
import java.awt.Component;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import javax.swing.ListModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

public class JListExt extends JList {

    /* Serial version of this class */
    private static final long serialVersionUID = 0;

    private JScrollPane scroller;

    /**
     * Instance initializer
     */
    public JListExt() {
        super(new DefaultListModel());
    }
    
    public JListExt setValues(Iterable<?> values) {
        DefaultListModel model = new DefaultListModel();
        for (Object object : values) {
            model.addElement(object);
        }
        setModel(model);
        return this;
    }
    
    public void sort(final Function2<Object, Object, Integer> sorter) {
        ArrayList<Object> values = collectValues(Object.class, new ArrayList<Object>());
        Collections.sort(values, new Comparator<Object>() {

            public int compare(Object o1, Object o2) {
                return sorter.apply(o1, o2);
            }
        });
        setValues(values);
    }
    
    /** Scan the values of this jlist and adds the items of the requested type to
     the passed list */
    public <A, B extends List<A>> B collectValues(Class<A> type, B list) {
        DefaultListModel model = getModel();
        for (int i = 0; i < model.getSize(); i++) {
            Object value = model.get(i);
            if(type.isAssignableFrom(value.getClass())) list.add(type.cast(value));
        }
        return list;
    }
    
    /** Appends an element to the list */
    public void append(Object item) {
        getModel().addElement(item);
    }
    
    /** true if this list contains the given value */
    public boolean containsValue(Object value) {
        return getModel().contains(value);
    }
    
    /** Executes a function for each element of the list of the given type */
    public <T> void foreach(Class<T> type, Function1<T, ?> fun) {
        DefaultListModel model = getModel();
        for (int i = 0; i < model.getSize(); i++) {
            Object value = model.get(i);
            if(type.isAssignableFrom(value.getClass())) fun.apply(type.cast(value));
        }
    }
    
    /**
     * Removes all elements and return this
     */
    public JListExt clear() {
        getModel().removeAllElements();
        return this;
    }
    
    /**
     * Add an element to the head of this list
     */
    public JListExt push(Object item) {
        getModel().add(0, item);
        return this;
    }
    
    /**
     * Removes the selected element (if any) and returns this
     */
    public JListExt removeSelectedElement() {
        int index = getSelectedIndex();
        if(index >= 0) {
            getModel().remove(index);
        }
        return this;
    }

    @Override
    public DefaultListModel getModel() {
        return (DefaultListModel) super.getModel();
    }

    @Override
    public void setModel(ListModel model) {
        if(!(model instanceof DefaultListModel)) {
            throw new IllegalArgumentException("model is not a DefaultListModel instance");
        }
        super.setModel(model);
    }

    public JListExt withRenderer(final Function2<Object, Boolean, Component> renderer) {
        setCellRenderer(new ListCellRenderer() {

            public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
                return renderer.apply(value, isSelected);
            }
        });
        return this;
    }

    public JListExt withSimpleRenderer(final Function2<Object, JLabel, ?> renderer) {
        setCellRenderer(new DefaultListCellRenderer() {

            /* Serial version of this class */
            private static final long serialVersionUID = 0;

            @Override
            public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
                JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
                renderer.apply(value, label);
                return label;
            }


        });
        return this;
    }

    public JListExt withItems(Iterable<?> items) {
        DefaultListModel model = getModel();
        model.removeAllElements();
        for (Object object : items) {
            model.addElement(object);
        }
        return this;
    }

    public JListExt append(Iterable<?> items) {
        DefaultListModel model = getModel();
        for (Object object : items) {
            model.addElement(object);
        }
        return this;
    }

    public JScrollPane getScroller() {
        if(scroller == null) scroller = new JScrollPane(this);
        scroller.setName(getName());
        return scroller;
    }
    
    /**
     * Executes a function when the user double-clicks an item.
     * @param callback the function applied to the double-clicked item, if any
     * @return this
     */
    public JListExt onDoubleClick(final Function1<Object, ?> callback) {
        addMouseListener(new InputAdapter() {

            @Override
            protected void onLeftMousePressed(MouseEventExt e) {
                if(e.isDoubleClick()) {
                    int index = locationToIndex(e.getPoint());
                    if(index >= 0) {
                        callback.apply(getValueAtIndex(index));
                    }
                }
            }
        });
        return this;
    }
    
    public Object getValueAtIndex(int index) {
        return getModel().get(index);
    }
    
    public void removeItem(Object item) {
        getModel().removeElement(item);
    }

    public JListExt onSelectionSet(final Runnable runnable) {
        addListSelectionListener(new ListSelectionListener() {

            public void valueChanged(ListSelectionEvent e) {
                if(!e.getValueIsAdjusting()) {
                    runnable.run();
                }
            }
        });
        return this;
    }

    public JListExt withName(String string) {
        setName(string);
        return this;
    }
}
